100% Pure Java Cookbook

Rules and Hints for Maximizing the Portability of Java Programs

About this Guide

This document is a "developer's style guide" for developers who want to maximize the portability of their Java programs. It explains the important difference between merely writing in Java, and writing effective and portable Java programs that will run on any Java Compatible platform or device.

The guide presents the definition of 100% Pure Java and the rules for compliance with 100% Pure Java standards. It also provides practical hints and advice for maximizing portability of Java programs, and offers workarounds for the most common portability pitfalls encountered when writing Java software.

The 100% Pure Java Cookbook enables Java developers and Sun customers to benefit from the collective experience of Sun's Java development team. The guide will be updated regularly with new information and ideas to help Java developers achieve maximum portability with their Java programs.

Certification Guide

A companion guide, the 100% Pure Java Certification Guide, details the exact steps entailed in preparing an application for certification as 100% Pure Java. Certification is required in order to use the 100% Pure Java trademark and logo, and is a hallmark for portable Java programs. The Certification Guide gives details of the procedures and tasks required for certification, as well as a list of steps to follow, with exit criteria for each step. A short overview of the certification process in included in this cookbook, as well. The Certification Guide is available separately from Sun.

This Cookbook gives the principles for certification, along with hints for portability. The Certification Guide gives the exact criteria used in qualifying a program for certification, along with steps required to prepare a program for submission for certification.

The Cookbook and Certification Guide are also available as part of the 100% Pure Java Certification Package, which includes everything (documents and tools) necessary to prepare a program for certification. The Certification Package is available after registration for certification.

The 100% Pure Java certification and branding program is intended to give customers confidence in products that wear the brand. Products that pass the 100% Pure Java certification tests are eligible to use the exclusive 100% Pure Java logo on their packaging. Our intention is to make the certification process one that improves the portability of Java programs, and hence to make the logo one that gives developers pride and customers confidence.

Contents

This guide has two purposes: it sets out the rules for achieving 100% Pure Java compliance, and it presents advice and examples to aid the development of fully portable Java software. It defines what 100% Pure Java is and, equally important, what it is not.

This edition of the Cookbook focuses on the definition of purity for applications, applets, and class libraries; future editions will add sections discussing purity for the other kinds of Java programs. The basic rules for purity are the same for all kinds of Java programs; some details of the certification process are specific to the kind of program being certified. This document sets out the principles of purity and outlines how those principles are applied to each kind of program; full details are in the Certification Guide.

The criteria used in inspecting a program for compliance with the 100% Pure Java standard are provided as an appendix to this cookbook, as they are derived directly from the JavaPureCheck program that does the initial inspection. The specific tests performed by JavaPureCheck will be improved and refined on an ongoing basis. To ensure that the most up-to-date version is always available, we encourage developers to download the latest version of the JavaPureCheck program from the SunTest Web site (http://www.suntest.com/100percent).

Java, Portability, and Purity

The Java Platform's ideal of "Write Once, Run Anywhere" promises streamlined software development and delivery. It saves developers time and it saves their companies the expense of multiple ports and traditional distribution methods. Unlike programming systems that tie developers to a single hardware platform, the Java Platform bridges many varieties of hardware and system software with a common language, opening up new markets rather than limiting market opportunities.

This portability brings new freedom to developers, who can now deliver solutions for a wide variety of hardware without the expense and delay of porting and qualification. It also brings new freedom to users, who can choose and change among all Java Compatible hardware at will.

Yet the inherent portability of the Java software platform alone does not ensure seamless operation for every Java program. A number of pitfalls can adversely affect the portability of Java programs, and developers need to know what they are and how to work around them. That is why Sun has developed this cookbook for writing 100% Pure Java programs.

A pure Java program is one that relies only on the documented and specified Java platform. By focusing on the purity of Java programs now, developers can reduce their exposure to portability risks in the future.

Portability vs. Purity

There is a critical distinction--and connection--between portability and purity.

Most people think of a portable program as "one that produces the same results on any platform." This is actually a very imprecise definition.

For example, consider this program:


class ShowOS { 
  public static void main(String[] args) {
    try {
      String osName = System.getProperty("os.name");
      System.out.println(osName);
    } catch (RuntimeException re) {
      System.err.println("Problem: " + re);
    }
  }
}

Code Example 1: class ShowOS

Most would say that this is a portable program, even though it produces different results on different platforms. Compare it to this program:


class BadOS { 
  public static void main(String[] args) {
    try {
      String osName = System.getProperty("os.name");
      if (osName.equals("Solaris")) {
        throw new RuntimeException();
      }
      System.out.println(osName);
    } catch (RuntimeException re) {
      System.err.println("Problem: "+ re);
    }
  }
}

Code Example 2: class BadOS

The difference between these two programs is not in the use of the Java platform; formally, they are nearly identical. The difference is in the functionality they implement. The second example implements an unportable specification in an apparently portable way.

We might be tempted to define a portable program as "one that fulfills its function on any platform." However, this definition is very hard to apply, because the "function" of each program is specific to that program. Using this definition, we cannot evaluate the portability of a program without (ultimately) understanding the requirements of its users; this definition blurs the distinction between portability and quality.

However, we can determine a simple kind of specification without understanding the requirements of the user: normal termination. The Java language defines two exit paths for any procedure: a normal termination, expressed as an exit or return, and an abrupt termination, expressed as a throw of an exception. Clearly a program has failed to operate normally when the program as a whole terminates abruptly due to an uncaught exception.

Purity Measured

Because portability is so dependent on the functionality of a particular program, we define the concept of "purity." Purity is the aspect of portability that we can measure by looking at the mechanics of how the program uses the platform interface, rather than looking at the program's functionality. A pure Java program is one that depends only on the Java Platform, as defined by the documentation. A computer that implements the Java Platform definition may be identified by the "Java Compatible" trademark.

Purity is measured at the bottom (platform) edge of the program, rather than at the top (user) edge. Purity is intended to be a good predictor of portability; a pure program should not be accidentally unportable.

The cookbook sets out the rules and principles for purity, along with hints and tips for the more general goal of portability.

The Measurement Process

The certification process for the 100% Pure Java brand is outlined in a subsequent section of this cookbook, and is detailed in the companion Certification Guide. In order to set the context for the sequel, we will mention here only that the certification process entails both a static inspection of the class files that make up your program and a dynamic test that involves running the program.

Purity is not Goodness

Not all good Java programs are pure; not all pure Java programs are good.

It is entirely possible to use Java to write a program that depends on platform-specific capabilities, defines native methods, in short violates all the rules for purity. It is no reflection on the quality of such a program to state that it is not intended to run on all Java Compatible computers; the program may meet the needs of its users in a robust, reliable, and efficient way. A program can be good without being pure.

Likewise, the measurements made to ascertain the purity of a program are intentionally blind to the user's requirements. A program can be completely pure and still be quite unsuited to the user's requirements, even be quite buggy. The purity process attempts to find out if the program will behave the same on all Java Compatible platforms, not if it will behave well on all platforms. A program can be pure without being good.

Purity is intended to be a measure of only one of the many virtues required of a program. It is not even a perfect measure of that one virtue, portability. It is nonetheless a useful measure; we have found that the purity measures do detect some common portability problems. The purity process does result in better portability; that is our goal, to increase the portability of Java programs.

The Scope of Purity

In order to discuss the purity of a Java program, we must have an understanding of the boundaries of our discussion.

What Is a Java Program?

For the purposes of purity checking, we define a program to be a self-contained set of classes, which have external dependencies only on the Java platform. A Java program may be an application, an applet, a class library, a servlet, a JavaBean, or more than one of the above.

Programs are checked for purity individually. For example, the client part of a client-server pair may be pure even though the server is not 100% pure. The question the purity check tries to answer is, "Does this program depend only on the Java platform?" Since the Java platform includes network classes, it is entirely possible to write a client in pure Java. It is also possible to write a server entirely in Java, independent of the existence of a pure client. The definition of purity stops at the edge of the Java Core API.

Which Java API?

The question of which Java Developer's Kit (JDK) version to write to does not have a clear-cut answer at present. Version 1.1 has many useful features that are lacking in v1.0, but is not as widely available. The Java Web site has helpful information about making the transition between v1.0 and v1.1: http://java.sun.com/products/jdk/1.1/compatible

If your circumstances allow you to move to 1.1, it is a good idea to do so. You will gain portability benefits along with the extra features. In particular, the conformance requirements for a Java 1.1 platform are much more stringent than those that were in place for the 1.0 platform; you can thus expect a greater uniformity between 1.1 platforms than between 1.0 platforms.

By writing to the 1.0 interfaces, you ensure the widest immediate audience. By writing to the 1.1 interface, you take advantage of the recent improvements and prepare for the future. The choice depends on your circumstances. Applications written to either version are eligible for certification.

What About Libraries?

For certification, you must submit a program that has no external dependencies other than the Java Core API. This is to ensure that the user experience is uniform. The intention is that a 100% Pure Java program will run on any Java Compatible platform, with no fine print about which extensions must be available. Libraries outside the Java Core API invoked by Java applications must be packaged with the program, and must be 100% Pure Java as well.

If your program uses a Java Standard Extension API, then you will need to provide an implementation of that API as part of the verification package. However, implementations of the Standard Extensions can be certified as 100% Pure Java. If you use such a certified Standard Extension implementation, you can be sure that the library will not introduce any impurity into your program. Information about the Standard Extension APIs can be found at the Java Web site.

When certifying your program, you won't need to worry about recertification of the entire library; you will only have to show that your use of the library does not introduce any impurity.

A similar situation holds for class libraries that are not Standard Extensions; they can be used freely in your program, as long as they are themselves pure and as long as they are delivered along with your program. If you use a class library that has not been certified as pure, then you are responsible for providing a dynamic test driver for that library; the classes in the library will not be distinguished from any other class in your program. If you use a class library that has been certified as 100% Pure Java, then you are absolved of the responsibility for code coverage for the library code.

To allow this absolution, a class library dynamic driver must achieve 100% code coverage for certification.

Rules for 100% Pure Java

This section provides a high-level overview of the principles a program must follow in order to meet 100% Pure Java standards.

Rule 1. No native methods

Attempting to introduce native code into a Java program results in the sacrifice of most of the benefits of Java: security, platform independence, garbage collection, and easy class loading over the network.

For users, the security issues of software that mixes Java and native method definition are substantial. There is no assurance that the code is virus-free; moreover, if a native method has a pointer overrun or attempts to access protected memory, it can crash the Java Virtual Machine, possibly corrupting and certainly interrupting the user's work.

Rule 2. No external dependencies aside from the Java Core APIs

The Java Core APIs (Application Program Interfaces) form a standard foundation for components, applets and applications; it is the essential framework for application development. 100% Pure Java applications must depend only on classes and interfaces documented in the Java Core API specification.

The Java Core APIs provide the basic language, utility, I/O, network, GUI, and applet services; vendors who have licensed Java have contracted to include them in any Java platform they deploy.

The specifications for all Java APIs are freely accessible on the World Wide Web at http://java.sun.com.

Rule 3. No use of undocumented parts of a Java implementation

Any implementation of the Java Core APIs will include classes and packages that are not part of the documented API interface. Portable programs must not depend on these implementation details, as they may vary between different Java implementations. This is true even if the classes in question are undocumented parts of the reference Java platform implementation from Sun. Those interfaces are not part of the Java platform definition, and they are not checked by the tests for Java Compatibility, so they may be absent or may behave in subtly and dangerously different ways on different Java implementations. They are not documented because they are not intended for client use.

One subtle way that a program may depend on implementation details is by defining classes into the packages that are part of the Core APIs or a specific implementation. This breaks protection boundaries that the Core implementors are entitled to count on.

Another subtle dependency on implementation details is direct use of the AWT component peer interfaces defined in classes in the java.awt.peer package. These interfaces are documented as being "for use by AWT implementors"; a portable program uses the AWT rather than implementing it.

Rule 4. No use of "tunnel" methods

Certain features of the Java language definition give the Java programmer access to hardware-specific code: native method definition (see Rule 1.), and some methods in the class java.lang.Runtime. This hardware access is very useful for writing programs that interface to legacy systems, but such interface programs are by definition not 100% Pure Java.

Recall that not every good program is pure.

There is a loophole in this Rule. The use of the Runtime.exec method is allowed if it's at the behest of the user of the program. For example, a command interpreter that executed programs named in the user's input could be written in pure Java. Another example is the invocation of an external program with a specific function, like a Web browser, as long as the user has control of which browser gets invoked.

The exact criteria for use of the Runtime.exec method are:

Rule 5. No hardwired platform specific constants

The java.io.File class can be used in an unportable way, by constructing Files using a platform-specific path constant. Similarly, input and output streams can be used unportably, with hard-coded and hardware-specific line termination characters. Fortunately, it is easy to avoid these sources of unportability, as the Java Core APIs provide portable alternatives.

Portability Pitfalls, Solutions, and Workarounds

This section identifies common problems encountered in programming for portability, and offers solutions or workarounds.

This is information about portability, not specifically about purity; some of these portability problems are detected in the purity checking process and some are not. Similarly, some of these pitfalls are very specific about certain method or class names or remedies, and others give principles rather than specifics.

In this portability cookbook, here are the recipes. Like all recipes, they must be tempered by the wisdom of the cook.

Pitfall: Thread scheduling

Explanation: Thread scheduling may differ on different platforms. If you rely on priorities or luck to prevent two threads from accessing the same object at the same time, your program is not portable.

For example, this program is not portable:

/** Example of unportable program. */
class Counter implements Runnable { 
  static long val = 0;

  public void run() { 
    val += 1;
  }

  public static void main(String[] args) {
    try { 
      Thread t1 = new Thread(new Counter());
      t1.setPriority(1);
      Thread t2 = new Thread(new Counter());
      t2.setPriority(2);

      t1.start();
      t2.start();

      t1.join();
      t2.join();

      System.out.println(val);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

This program may not print "2" on all platforms, even barring errors, because the two threads are not synchronized.

Unfortunately, this is a deep problem, and there is no quick check for its presence nor easy fix to prevent it from occurring.

Workaround: One simple, if drastic, answer is to make all methods synchronized. This may make some synchronization errors show up as obvious deadlocks rather than as silent data corruption. Unfortunately, there are examples of thread contention that will not be detected; for example, it will not detect the problem in the example Counter class, as the contention in the example is in field access rather than method access.

Solution: Adopt a discipline of multithreaded programming, as described in several textbooks. One that is specifically written for Java programmers is Concurrent Programming in Java by Doug Lea, Addison-Wesley 1997, ISBN 0-201-69581-2.

Pitfall: Use of native code

Explanation: In rare instances, it may seem necessary to include native methods to deliver access to a system resource that is not supported by Java or to boost performance of a particular application.

Solutions:

It may seem that you could confine the native methods to one class and provide an implementation of that class for every Java platform. However, this "solution" in fact only complicates matters, because the number of Java platforms is not fixed but is ever-increasing; and some Java platforms, such as the JavaStation, have no native code format aside from Java.

There is no 100% Pure Java workaround for native code. Even though it is possible that a program with native methods can be made portable (by writing a Java class or method to serve as a fallback for the native code), such a program cannot be certified as Pure because we know no principled way to show that the native-code and Java-only versions of such a program actually implement the same functionally.

If the two versions do, in fact, implement the same functionality, then the program without the native code is an equally capable 100% Pure Java program. That program is the one you can certify as 100% Pure Java.

The Java Native Method Interface (JNI) is not a way to make native code platform-independent; it is a way to make it easy to port native code. The native code still must be recompiled for each different hardware, and that recompilation will be difficult or impossible if the target hardware does not provide the library or the capabilities required by the native method.

Pitfall: Use of exec

Explanation: The java.lang.Runtime.exec method is not generally portable; not all platforms have applications that can be run, and not all platforms have the notion of "standard input" or "standard output". Certainly a hard-coded program name will not be portable; there is no program that has the same name on all Java platforms.

Solution: Don't use this method (except under the restrictions detailed in Rule 4).

Pitfall: Failure to use the portability features of Java Core API

Explanation: It is unportable to hard-code a line termination character, or to hard-code text display sizes.

Solution: Java provides two ways to make it easier to write a portable program. First, use the println method rather than printing Strings with embedded line termination characters; second, use the expression System.getProperty("line.separator") to find the line separator for the platform. The system properties in general are very useful for portability; use them whenever applicable.

Similarly, the AWT abstracts the details of coping with the platform's window system. Use that abstraction to the fullest: for example, use a LayoutManager rather than hard-coding component sizes or positions, use the various getSize methods, use the desktop colors available in java.awt.SystemColor.

Pitfall: Reflection

Explanation: The reflection facilities of JDK 1.1 are an immensely powerful addition to the language; they also make it harder to predict what a program will do. Method.invoke can be used to invoke any method, including ones that present portability problems.

Solution: Be careful.

Pitfall: Direct use of AWT peer classes

Explanation: Portable programs, that use rather than implement the AWT interfaces, should not use the AWT peer classes directly. The protocol for interaction with the peer classes is platform specific.

Solution: Stay on the client side of the AWT.

Pitfall: Misuse of System.exit

Explanation: The System.exit method forces termination of all threads in the Java virtual machine. This is drastic; it may, for example, destroy all windows created by the Java interpreter without giving the user a chance to record or even read their contents.

Solution: Java programs should usually terminate by stopping all nondaemon threads; in the simplest case of a command-line application, this is as easy as returning from the main method. System.exit should be reserved for a stop-the-world error exit, or for cases when a Java program is intended for use as a utility in a command script that may depend on the program's exit code.

Pitfall: Use of hard-coded file paths

Explanation: Hard-coded filenames may present portability problems. Hard-coded paths (with directory names joined to filenames) certainly do.

Solution: The most portable way to construct a File for a file in a directory is to use the File(File,String) constructor to build up the path. Other portable solutions are to use the system properties to get the local file separator and starting directory, or to use a file dialog to ask the user for a filename.

Note that the notion of an "absolute path" is somewhat system dependent; for example, Unix absolute paths all start with "/", while Windows absolute paths may start with any letter. For this reason, the use of an absolute path that is not derived from user input or from a system property is unportable.

Here is the code for a utility class that may make it more convenient to construct portable pathnames:

package util

import java.io.File;
import java.util.StringTokenizer;

/** A utility class to make it easier to use
  * java.io.File in a portable way.
  */

public class FileUtil {
  /** Create a new pathname by gluing together
    * a series of names.
    * If initial base is null, works from 
    * current directory.
    */
    public static File fromDir(File bse, String[] path) {
      File val = bse;
      int i = 0;

      if (val == null && path.length > 0) {
        val = new File(path[i++]);
      }

      for ( ; i < path.length; i++) {
        val = new File(val, path[i]);
      }

      return val;
    }

    public static File fromHere(String[] path) {
      return fromDir(null, path);
    }

    private static File fromProp(String propName) {
      String pd = System.getProperty(propName);
      return new File(pd);
    }

    /** A File for system property "user.dir".
      */
    public static File userDir() {
      return fromProp("user.dir");
    }

    /** A File for system property "java.home".
      */
    public static File javaHome() {
      return fromProp("java.home");
    }

    /** A File for system property "user.home".
      */
    public static File userHome() {
      return fromProp("user.home");
    }

    /** Split first argument, using second arg
      * as separator char.
      * Convenient for creating a portable pathname.
      */
    public static String[] split(String p, String sep) {
      StringTokenizer st = 
        new StringTokenizer(p, sep);
      String[] val = new String[st.countTokens()];

      for (int i = 0; i < val.length; i++) {
        val[i] = st.nextToken();
      }

      return val;
    }
  }

Pitfall: Line termination

Explanation: Different platforms have different conventions for line termination in a text file.

Solution: Use println, or use the line.separator property for output. For input, use the readLine methods.

Pitfall: Unportable command-line programs

Explanation: Command-line programs that use System.in, System.out, or System.err may be less than perfectly portable, as not all Java platforms have the concept of standard input or output streams.

Solution: Consider using a GUI, at least as an alternative--some platforms don't have a command line.

Pitfall: Command line processing

Explanation: Java leaves command line processing up to the programmer. However, the syntax and conventions are quite different on the different platforms.

Solution: The most portable answer is not to use the command line, but that's no good for batch programs that need to be driven from a script. So use the POSIX convention (options indicated with a dash) when processing command line options; it's a widely understood standard. Provide, at least as an alternative, a GUI, or read options from properties files (and document the properties).

Pitfall: Internationalization

Explanation: Java computing is a worldwide phenomenon. Particularly if your program is an applet, it is highly likely to be run in an environment with a different native language than yours. It is a basic courtesy to at least attempt to speak the language of the country where you find yourself.

Advice: Use the internationalization and localization features of JDK 1.1. Complete documentation is included in the 1.1 JDK, and is also available at http://java.sun.com/products/jdk/1.1/docs/guide/intl

Pitfall: Unicode rendering

Explanation: Not all platforms can render all Unicode characters.

Workaround: For the default text of messages, buttons, labels, and menus, use only ASCII. Of course, it is all right to use non-ASCII in localization resources and in text obtained from the user.

Pitfall: File I/O

Explanation: The 1.0 input and output classes are not portable to platforms with non-ASCII native file formats.

Solution: Use the 1.1 reader & writer classes.

Pitfall: GUI element size

Explanation: The exact size of the AWT elements will differ from platform to platform, as will the size of the screen and the default and maximum size of a window. Any hard-coded positions or sizes will run afoul of these variations.

Solution: Use a layout manager.

Pitfall: GUI fonts

Explanation: The size and availability of fonts varies from display to display (even on the same hardware platform, depending on installation).

Solution: Don't hard-code text sizes; let text elements assume their natural size in a layout, and use the FontMetrics methods to find the actual displayed size of a string on a Canvas. When setting a nondefault font, be sure to implement a fallback in the "catch" block. When creating a font menu, get the font names from the java.awt.Toolkit.getFontList method rather than using a hardwired font list.

When updating a program from 1.0 to 1.1, be sure to update the font names as described in the documentation for the java.awt.Toolkit.getFontList method:

Pitfall: GUI appearance

Explanation: The size of the screen and the number of available colors may change from platform to platform, or even user-to-user or day-to-day. This can make a display illegible (for example, black text on a dark blue background), or can hide buttons if they display off the screen.

Solution: It may be necessary to adjust font size or default window size according to screen resolution, which may be obtained by

Toolkit tk = java.awt.Toolkit.getDefaultToolkit();
int dpi = tk.getScreenResolution();

Applets are probably safest to use the default font, as it may have been customized by the user according to their personal preferences.

For an application, you may wish to give the user an option or control to select among several appearances, so they can choose one that suits their display and mood.

If writing to the 1.1 interface, you can make your colors harmonize with the user's desktop by using the colors from the java.awt.SystemColor class.

If you choose to use your own color scheme instead, beware that displays vary greatly in the color displayed for a particular RGB triple. Your program's appearance will be more portable if you use named color fields from java.awt.Color rather than numeric colors.

Pitfall: The Paint Protocol

Explanation: The AWT Component.paint and Component.update methods take a Graphics object as a parameter. This object should not be retained, as it may be a transient object valid only during the paint; an AWT implementation is free to destroy that Graphics object after the paint method returns, which makes it pointless (and dangerous) to retain the object.

Solution: Do not retain the paint Graphics object. In general, it is not safe to paint outside a call to update() (or paint(), which is called by the default Component.update()). If you do need a long-lived Graphics object, you should create a new one from the argument value, but be warned that this may fail on some platforms:

Graphics myGraphics = null; // retained

void paint(Graphics g) {
  if (myGraphics == null) {
    myGraphics = g.create();
  }
  // painting code goes here
}

Pitfall: Mixed event models

Explanation: The JDK 1.1 AWT uses a different event model from the previous AWT. Programs written to the 1.0.2 event model will work on a 1.1 platform, but mixing the two event models in one program is not guaranteed to work (rather the opposite: it's very likely not to work).

Solution: Stick to one event model per program. Don't try to convert a program gradually from the old event model to the new; bite the bullet and do it all at once.

Pitfall: Use of deprecated methods

Explanation: Certain methods from the Java Core APIs have been marked as deprecated. While these methods work in the current release, they are slated for removal at some point.

Advice: When you work on a class that uses deprecated methods, it is probably a good idea to update the class to the replacement as suggested in the API documentation, in order to get a head start on future work.

Pitfall: The Object.hashCode and Object.equals methods

Explanation: The Object.hashCode method returns a number which is effectively random and implementation and instance dependent. This has several consequences:

Solution: If a repeatable (although unpredictable) order of enumeration over the elements in a Hashtable is important to your program, then any object used as a key in the Hashtable should have a class-specific hashCode method which returns a value computed from data fields of the object.

If any of your classes defines an equals method, it must also define a hashCode method, such that for any two objects a,b of that class, a.equals(b) implies that a.hashCode() == b.hashCode(). Note that hashCode should not depend on any mutable property. If an object's hashCode value changes, that object is very likely to become unfindable.

Pitfall: Installation issues

Explanation: There are a series of problems that can arise when installing on various platforms, due to limitations or restrictions on filenames. This may interfere with the convention that the Java Virtual Machine uses to locate required class files, which depends on a simple mapping from class name to filename.

The problems may occur when installing, or when attempting to run the installed program.

The problematic restrictions are:

Workarounds: Package your classes into a JAR archive. JAR files are part of the JDK starting with version 1.1. This will work around the problems of long class names and of case distinctions. It may not work around the problem of non-ASCII class names.

If you are writing for the JDK 1.0 platform, a ZIP file is a possible alternative.

Solution: Either rename your packages and classes, or use a JAR file.

Pitfall: Hostname format

Explanation: The format of the string returned by the java.net.InetAddress.getHostName method depends on the hardware platform. In some cases, it will be a fully-qualified domain name; in others, it will only be the host part of that name.

Workaround: In most cases, this problem, which is a case of under specification, will pose no practical problem, as the unqualified name and the fully-qualified name can be used for identification and connection within a domain. In cases where the name must be exported to distant hosts, it may be best to give the IP number in addition to the host name. The IP number is available from the getAddress method.

Portability Hints

This section presents general hints for writing portable Java code.

Hints for Writing Portable Applets

Writing portable applets is somewhat harder than writing portable programs. An applet is, formally, a class that extends java.applet.Applet. In actuality, an applet's portability situation includes the Web page the applet loads from, the other classes the applet uses, the HTML that loads the applet, and the security manager and AppletContext of the user's browser.

One particular subtle possible problem in the HTML <applet> tag is that the contents of the applet markup must follow these rules:

Applets will almost certainly have to run under control of a security manager. However, there is no standard profile for security managers. The user can instruct their security manager to deny any combination of access. The best answer is to make sure that your applet handles any security exception gracefully.

Similarly, the Java standard does not specify required content types or protocols. The MIME types image/jpeg and image/gif are probably safe, as are the http:, file:, and ftp: protocols. Again, handle any errors gracefully.

In the Java 1.0 API, the specification of the AppletContext/Applet protocol was not very precise. As a consequence, different browsers call the applet's enter & leave methods at different times. The protocol specification is much more precise in the Java 1.1 API, so this problem will disappear in time. Meanwhile, beware that an applet that behaves politely on one browser may hog resources on another, while a different applet may function normally on one browser but stall on another.

Security Hints

If your program will run under control of a SecurityManager (for example, if it's an applet) then it may encounter a SecurityException. This is an unchecked exception, so it may be thrown without being declared.

Depending on the particular SecurityManager, the user can restrict any access to protected resources attempted by the program. Therefore, it will greatly enhance the portability of your program if you catch any SecurityException and report the situation to the user in a polite manner.

Coping with Bugs

There are bugs in some Java implementations. In order to be most portable, it is courteous to work around those bugs. It is helpful to consult the known bug lists available from Sun and from some browser makers. Bugs are harder to cope with than features, because they are less well defined and less well designed.

Workarounds: In some cases, a program will have to include different code for different platforms. Workarounds are expensive, both to code and to test. They are unfortunately inescapable in some circumstances.

In general, you can activate a workaround either proactively by branching on system properties, such as java.class.version and os.name, or reactively by putting the workaround in an exception handler. The choice depends on the character of the workaround. If the structure of the program or of long-lived data structures is affected, then it's probably best to be proactive. If the workaround has relatively local effect, then a reactive fallback may be easiest to code and understand. When possible, the reactive fallback will probably perform better when the fallback is not taken, as the cost of establishing an exception handler in Java is quite low.

A preemptive fallback (perhaps a fall-forward?) is required if the normal code path would cause irreversible changes before the problem is detected.

Here's an example of a preemptive workaround:

class Preemptive {
  private boolean hasFooBug() {
    String java_name = System.getProperty("java.maker");
      return java_name.equals("KnownBad");
  }

  void something() {
    if (hasFooBug()) {
      workaround();
    } else {
      // normal code path
    }
  }
}

Code Example 3: class Preemptive

And here is an example of a reactive workaround:

class Reactive {
  void something() {
    try {
      // normal code path
    } catch (Exception e) {
      workaround();
    }
  }
}

Code Example 4: class Reactive

Certification of Purity

Developers who have followed the rules for writing universally portable Java programs may wish to pursue certification of their products as 100% Pure Java. Sun has created a 100% Pure Java Certification process to facilitate portability testing. Products that pass the test may use the 100% Pure Java logo in their packaging as a way of assuring customers that the product does indeed run on any Java Compatible device.

This section gives a brief outline of the testing and assurance process. Full details are set forth in the 100% Pure Java Certification Guide.

Certification Process

The certification process includes the following steps:

The 100% Pure Java brand is awarded upon successful completion of a verification process conducted by an independent third-party Certification Center. That verification process is intended to check that your program meets the standards of the 100% Pure Java brand, to protect the value of the brand and ensure that consumers view it as an indicator of quality and portability.

You can perform the same Certification Center tests in-house as part of your development process. When you submit a package for assurance, you should be able to predict the result with high accuracy.

Checking for Purity

The steps involved in checking a program for purity are:

Static Check

The simplest portability check is a static check of the program. This is performed by JavaPureCheck, a program that reads the .class files that make up the program and checks constant definitions and method references for portability errors, warnings, and advice.

JavaPureCheck can be run on any class file, at any stage of development, to give portability hints and information. It can be used to check your program as soon as the first class is compiled; we recommend that you make it part of your development process so that you can correct portability problems early.

The static check performed by JavaPureCheck is valuable, but imperfect. In many cases, it alerts the user to potential portability problems that may not be a problem in practice. That is why the submitted program is executed as part of the verification process.

Installation and Check

The Certification Center runs the program on the reference platforms, under human control, as a smoke test. (Smoke is, of course, the essential component in any electrical equipment-if the smoke gets out, the equipment ceases to work).

This is not a thorough test of the program; the intent is to exercise the program enough to ensure that the installation was successful.

Dynamic Test

Because the static check is not perfectly precise, the verification package must include a test driver that exercises the program. Because Java is the only computing environment that is defined to be supported on all the reference platforms, the test driver should be one or more Java programs.

In some cases, it will be impossible to construct a Java program to drive a certain capability of the tested program. In such cases, it is permissible to provide instructions for manual exercise of the program.

Standards for Portability

The ideal of the 100% Pure Java initiative is to assure that the end user has a seamless and painless experience on any conforming Java platform. Identical behavior is not the standard; a portable program might run differently on different platforms.

For some programs, portability is not a property of the program, but of the program's input. As an example, consider a program that parses and interprets Java statements. This program will necessarily behave unportably when given an unportable program to interpret. As long as the unportability is not intrinsic to the program, it is eligible for 100% Pure Java Certification. The software's developer must be the final authority on the program's correct behavior.

Similarly, the appearance of the program (if it has a GUI) is the responsibility of the developer. The standard for certification is that the program display essentially the same elements on all platforms, not that those elements have the same appearance.

Certification of Various Kinds of Programs

Java programs can be used in various ways: as applets within an HTML document on the World Wide Web, as stand-alone application programs, as components in a compound document, and as services within a Web server. While each of these uses imposes its own variation on the conditions for purity, they all fall within the rules set forth above.

Certification for Applets

The distinctive considerations for Java applets are that they exist inside an HTML document, displayed by a browser. Therefore the platform interface for an applet is not just the Java Core API library and virtual machine, but also the browser, as abstracted by the AppletContext class.

The goal of the certification process for an applet is to identify problems that might impede the applet's function on an arbitrary browser. This includes all the problems that might impede application portability. In addition, applets face issues of access control as implemented by the various SecurityManager implementations, and issues of the Applet/AppletContext protocol.

Because an Applet is, by nature, exposed to a wide variety of Java platform implementations in a wide variety of browsers on a wide variety of hardware platforms, issues of portability loom large for applets. While the certification process can address some of those issues, it must be emphasized that purity is not identical to portability. A 100% Pure Java applet may fail to operate correctly in a particular browser for a wide variety of reasons: the browser may have bugs, the applet may implement an unportable functionality in a pure way (as in Code Example 2 above), or the applet may depend on specific behavior that is outside the scope of the Java platform specification but in a way not measured by the certification process.

Given these caveats, the purity rules and the criteria are the same for applets as for any Java program. The details of the dynamic test part of the verification process are slightly different, of course, because the applet must be exercised within the context of a browser. The criteria for verification is that the applet must be exercised in more than one browser on more than one hardware platform. Details are in the Certification Guide.

Certification for Class Libraries

A collection of Java classes can be certified as 100% Pure Java. The rules for certification are the same as for any other Java program, including completeness (that is, a library must be delivered in a self-contained form, with external dependencies only on the Java Core API, to be certified).

The code coverage criteria for the dynamic test required as part of the certification process are stricter for libraries than for other kinds of Java programs. In return for this stricter initial checking, users of a certified library are absolved of the responsibility for coverage of the library in their dynamic test.

Certification for JavaBeans

JavaBeans are another important kind of Java program, distinct from both applications and applets (although one program can be a Bean, an application, and an applet simultaneously). The portability situation for JavaBeans is similar to that for applets, in that they face additional security and protocol considerations not faced by application programs.

The rules for purity, and the static check criteria are the same for Beans as for any other Java program. Note that this means, in particular, that a JavaBean validation package does not need to include an instantiation of any Listener interfaces even though the Bean may have references to such instances.

The dynamic test criteria for a Bean is similar to that for a library: the test driver must achieve 100% method coverage of the public methods, because a Bean may be exercised in arbitrary ways in practical use. The reference test context for the dynamic test of a Bean is the BeanBox delivered as part of the Bean Developer's Kit.

Certification Process Details and Updates

Complete details of the 100% Pure Java certification process, including tool access and detailed instructions, is available from the 100% Pure Java Web site.

From time-to-time, Sun will update and improve the certification process. Certification will always require both a static check and a dynamic test. As Sun improves the process, the information will become more and more precisely targeted to portability problems. Information on the current process will always be available at the Web site.

Conclusion

The Java software platform streamlines software development and delivery, saving developers the expense of multiple ports and traditional distribution methods. However, despite the "Run Anywhere" nature of Java, it is still possible to write an unportable Java program.

By focusing on the portability of Java programs now, you can avoid having to modify your programs for portability in the future. And by certifying your products as 100% Pure Java, you can achieve several important benefits for yourself and your company--from higher productivity to increased sales and faster end-user adoption of the Java software platform.

Hopefully, by describing the requirements for achieving 100% Pure Java compliance, this guide will facilitate the development of software that truly delivers the portability benefits that are bringing customers to Java.

For More Information

Consult the 100% Pure Java home page on the Web for additional information about the 100% Pure Java initiative and for updates.

For more information about various Java-related topics, visit the following Web sites:

The exact rules used by the static checker, JavaPureCheck, are documented by HTML files generated by the program. For your convenience, copies of that generated documentation are here:

  • Rules for the 1.0.2 version of the Java Platform: jdk102.html
  • Rules for all 1.1 versions of the Java Platform: jdk11.html
  • Copyright © 1997, Sun Microsystems Corporation. All rights reserved.